使用 QT6 实现截图功能

使用 QT6 实现截图功能

实现简介

改了一下 Qt项目中,实现屏幕截图功能的模块详细实现 的代码

  • 截图区域的获取。监听鼠标点击,移动,释放事件,获取需要截图区域的左上角和右下角的坐标。
  • 截图区域的绘制。使用 drawPixmap 绘制截图区域。
  • 截图的保存。通过 qt 提供的方法保存文件即可。
  • 其他,鼠标右键的选项菜单通过 menu 实现,使用槽机制添加各个选项的对应方法。

学习总结

QMenu 创建,及右键菜单点击事件

在截图区域点击鼠标右键,会唤起菜单。主要实现是通过 contextMenuEvent 事件和 QMenu 实现。

主要代码

1
2
3
4
5
6
7
// 创建 menu
menu = new QMenu(this);
menu->addAction("保存当前截图", this, SLOT(saveScreen()));
menu->addAction("保存全屏截图", this, SLOT(saveFullScreen()));
menu->addAction("截图另存为", this, SLOT(saveScreenOther()));
menu->addAction("全屏另存为", this, SLOT(saveFullOther()));
menu->addAction("退出截图", this, SLOT(hide()));
1
2
3
4
5
6
// contextMenuEvent 右键菜单点击事件
void ScreenWidget::contextMenuEvent(QContextMenuEvent *)
{
this->setCursor(Qt::ArrowCursor);
menu->exec(cursor().pos());
}

鼠标点击,释放,移动事件

通过重写 Widget 的方法来实现鼠标相关事件的监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 鼠标点击事件
void ScreenWidget::mousePressEvent(QMouseEvent *e)
{
int status = screen->getStatus();

// 第一次点击鼠标,设置开始位置
if (status == Screen::CLICK) {
screen->setStart(e->pos());
} else if (status == Screen::MOV) {
// 此时为 move 状态,鼠标点击事件已经释放
if (screen->isInArea(e->pos()) == false) {
// 光标不在截图区域里面,重新截图
screen->setStart(e->pos());
screen->setStatus(Screen::CLICK);
} else {
// 光标在截图区域里面,需要移动截图区域
movPos = e->pos();
this->setCursor(Qt::SizeAllCursor);
}
}

this->update();
}

// 鼠标移动事件
void ScreenWidget::mouseMoveEvent(QMouseEvent *e)
{
int status = screen->getStatus();

if (status == Screen::CLICK) {
// 鼠标处于点击状态则持续更新鼠标落点
screen->setEnd(e->pos());
} else if (screen->getStatus() == Screen::MOV) {
QPoint p(e->x() - movPos.x(), e->y() - movPos.y());
screen->move(p);
movPos = e->pos();
}

this->update();
}

// 鼠标释放事件
void ScreenWidget::mouseReleaseEvent(QMouseEvent *e)
{
if (screen->getStatus() == Screen::CLICK) {
// 鼠标释放了,结束移动状态
screen->setStatus(Screen::MOV);
} else if (screen->getStatus() == Screen::MOV) {
// 截图区域移动结束
this->setCursor(Qt::ArrowCursor);
}

this->update();
}

通常会读取 e 对象中的 button 来判断是鼠标左边点击还是哪里点击。

这里使用了自己定义的状态,是因为截图时会点击鼠标并移动(同时触发点击与移动事件,就不能通过简单的判断事件类型去定义起始坐标),使用自己定义的状态可以更好的管理截图区域的坐标。

paintEvent, showEvent,drawPixmap

截图区域的绘制主要就是通过 QPainter 实现的。

showEvent 会在窗口显示时自动调用,在这个程序里就是实例被创建且调用后。(这也是为什么程序运行后全屏会变为灰色,是 showEvent 绘制出来的背景。

paintEvent 的首次调用是所有 ui 绘制完成后,然后就是 ui 发生变化时触发。在这个程序中,当鼠标点击,鼠标移动以及鼠标释放后,都会执行一句 this->update(),更新 ui,随后触发 paintEvent,在 paintEvent 这个回调函数中通过 drawPixmap 配合坐标和缩放因子绘制截图区域。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void ScreenWidget::paintEvent(QPaintEvent *)
{
int x = screen->getLeftUp().x();
int y = screen->getLeftUp().y();
int w = screen->getRightDown().x() - x;
int h = screen->getRightDown().y() - y;

QPainter painter(this);

// 设置绿色边框样式
QPen pen;
pen.setColor(Qt::green);
pen.setWidth(2); // 设置边框宽度
pen.setStyle(Qt::DotLine);
painter.setPen(pen);

// 绘制半透明背景
painter.drawPixmap(0, 0, *bgScreen);

if (w > 0 && h > 0) {
// 根据缩放因子调整截图区域的实际坐标和尺寸
QRect sourceRect(x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio);
QRect targetRect(x, y, w, h);

// 绘制截图区域内容
painter.drawPixmap(targetRect, *fullScreen, sourceRect);
}

// 绘制绿色边框
painter.drawRect(x, y, w, h);

// 绘制尺寸信息文本
pen.setColor(Qt::black);
painter.setPen(pen);
painter.drawText(x + w + 2, y + 12,
tr("%5 x %6")
.arg(w * pixelRatio)
.arg(h * pixelRatio));
}


void ScreenWidget::showEvent(QShowEvent *)
{
QPoint point(-1, -1);
screen->setStart(point);
screen->setEnd(point);

QScreen *pscreen = QApplication::primaryScreen();
// 默认当前窗口
*fullScreen = pscreen->grabWindow(0, 0, 0, screen->width(), screen->height());
printf("width: %d, height: %d", fullScreen->width(), fullScreen->height());

//设置透明度实现模糊背景
QPixmap pix(screen->width(), screen->height());
pix.fill((QColor(160, 160, 160, 200)));
bgScreen = new QPixmap(*fullScreen);
QPainter p(bgScreen);
p.drawPixmap(0, 0, pix);
}

遇到的问题

QT5 不再支持 QDesktopWidget

源代码中使用 QDesktopWidget 获取屏幕尺寸,QT6 开始不支持这个类了。可以使用 QGuiApplication 代替。通过 QGuiApplication::primaryScreen() 获取。

源代码在本机测试,截图时截图区域会放大,并且边框与图片区域有 padding

原因:在高 DPI 显示器上,系统会对屏幕内容进行缩放(例如 150% 或 200%)。在抓取屏幕时,如果没有考虑高 DPI 缩放因子,就会导致捕获图像在显示时出现放大的情况。

解决方案:使用 QApplication::primaryScreen()->devicePixelRatio() 获取缩放因子,在截图区域绘制时,绘制的坐标都与缩放因子相乘,就可以得到正确的绘制区域。伪代码如下:

1
2
3
4
5
6
7
8
9
qreal pixelRatio = QApplication::primaryScreen()->devicePixelRatio();
if (w > 0 && h > 0) {
// 根据缩放因子调整截图区域的实际坐标和尺寸
QRect sourceRect(x * pixelRatio, y * pixelRatio, w * pixelRatio, h * pixelRatio);
QRect targetRect(x, y, w, h);

// 绘制截图区域内容
painter.drawPixmap(targetRect, *fullScreen, sourceRect);
}

不仅在绘制截图区域的时候需要考虑缩放因子,在截图区域保存时也需要考虑这个问题。

下一步计划

  • 添加开始界面
  • 可以反复截图
  • 出现截图菜单,可以复制到剪切板
  • 实现常见截图软件应该有的功能